package cn.edu.dgut.css.sai.security.oauth2.config;

import cn.edu.dgut.css.sai.security.oauth2.client.endpoint.DelegateSaiOAuth2AccessTokenResponseClient;
import cn.edu.dgut.css.sai.security.oauth2.client.userinfo.DelegateSaiOAuth2UserService;
import cn.edu.dgut.css.sai.security.oauth2.client.web.DelegateSaiOAuth2AuthorizationRequestResolver;
import cn.edu.dgut.css.sai.security.oauth2.filter.DgutAuthorizationResponseFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.DefaultSecurityFilterChain;

/**
 * @author Sai
 * @since 1.0
 * 2018-8-27
 */

@SuppressWarnings("unused")
public final class SaiOAuth2LoginSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = new DelegateSaiOAuth2AccessTokenResponseClient();
    private final OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new DelegateSaiOAuth2UserService();

    /**
     * 配置oauth2login()
     * <p>
     * 注意：这里"必须"写在init()里，不能写在configure()
     * <br>
     * 主要是因为oauth2login()使用了{@code HttpSecurity#getOrApply(SecurityConfigurerAdapter)}
     * <br>
     * 创建一个{@link OAuth2LoginConfigurer}的实例，并add进列表，
     * <br>
     * 但在{@code HttpSecurity#doBuild()}的configure()阶段，执行{@code HttpSecurity#add(SecurityConfigurer)}时会抛异常，
     * <br>
     * 这是因为{@code HttpSecurity#add(SecurityConfigurer)}方法里会判断{@code HttpSecurity#buildState}，
     * <br>
     * 在{@code HttpSecurity#doBuild()}的configure()阶段，buildState.isConfigured()返回true，
     * <br>
     * 从而导致抛异常IllegalStateException("Cannot apply " + configurer + " to already built object")；
     * <p>
     * 所以，配置oauth2login()的代码只能写在builder的init()阶段，否则抛异常。
     * <p>
     *
     * @param http HttpSecurity builder
     * @see HttpSecurity
     * @see SecurityConfigurerAdapter
     * @see SecurityConfigurer
     * @see OAuth2LoginConfigurer
     */
    @Override
    public void init(HttpSecurity http) throws Exception {
        // 添加一个filter,过滤dgut登录时的token参数改名为code
        http.addFilterBefore(postProcess(new DgutAuthorizationResponseFilter()), OAuth2LoginAuthenticationFilter.class);

        // 从spring容器中获取 ClientRegistrationRepository 和 SaiOAuth2ConfigProperties 。
        ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
        ClientRegistrationRepository clientRegistrationRepository = applicationContext.getBean(ClientRegistrationRepository.class);
        SaiOAuth2ConfigProperties saiOAuth2ConfigProperties = applicationContext.getBean(SaiOAuth2ConfigProperties.class);

        // 配置 OAuth2LoginConfigurer
        http.oauth2Login()
                .authorizationEndpoint()
                    .baseUri(saiOAuth2ConfigProperties.getAuthorizationRequestBaseUri())
                    .authorizationRequestResolver(new DelegateSaiOAuth2AuthorizationRequestResolver(clientRegistrationRepository, saiOAuth2ConfigProperties.getAuthorizationRequestBaseUri()))
                    .and()
                .tokenEndpoint()
                    .accessTokenResponseClient(accessTokenResponseClient)
                    .and()
                .redirectionEndpoint()
                    .baseUri(saiOAuth2ConfigProperties.getAuthorizationResponseBaseUri())
                    .and()
                .userInfoEndpoint()
                    .userService(oauth2UserService);
    }
}
